/* 
 *  Copyright 2012 CodeMagi, Inc.
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.codemagi.servlets.content;

import com.codemagi.login.AccessException;
import com.codemagi.login.LoginException;
import com.codemagi.servlets.*;
import com.codemagi.servlets.model.*;
import com.codemagi.servlets.upload.*;
import com.codemagi.servlets.upload.model.*;
import com.codemagi.util.*;
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.commons.fileupload.*;
import org.apache.log4j.*;
import org.exolab.castor.jdo.CacheManager;
import org.exolab.castor.jdo.Database;
import org.exolab.castor.jdo.PersistenceException;

/**
 * Class extending the <code>HttpServlet</code> to implement
 * the controller for uploading files for a specific page *body*.
 * <P>
 * The following actions are supported: upload, modify, select, delete
 * <p>
 * Please see the related documentation for more detailed
 * information on process flow and functionality.
 *
 * @version 1.0
 * @author August Detlefsen for CodeMagi, Inc.
 */
public class PageUploadController extends UploadController {

    //set to true to view debug output
    Logger log = Logger.getLogger(this.getClass());

    protected String fileUploadLink     = "/htmlarea/popups/file_upload.jsp?error=1&pageId=";
    protected String messageLink        = "/htmlarea/popups/upload_message.jsp";
    protected String insertImageLink    = "/htmlarea/popups/insert_image.jsp";
    protected Class  USER_CLASS         = com.codemagi.login.model.User.class;
    protected Class  UPLOAD_CLASS       = com.codemagi.servlets.content.model.PageUpload.class;

    public void init(ServletConfig config)
        throws ServletException {


	log.debug("UploadClass before init: " + UPLOAD_CLASS);

        super.init(config);

	log.debug("UploadClass after init: " + UPLOAD_CLASS);

	UPLOAD_CLASS   = com.codemagi.servlets.content.model.PageUpload.class;

	log.debug("UploadClass after re-assignment: " + UPLOAD_CLASS);

        //get params for thumbnails
        String userClassName = config.getInitParameter("userClass");

	if (!Utils.isEmpty(userClassName)) {
	    try {
		USER_CLASS = Class.forName(userClassName);
		log.debug("User class from web.xml config OK :" + userClassName);
	    } catch (ClassNotFoundException ce) {
		log.debug("CONFIG ERROR: User class from web.xml config NOT FOUND: " + userClassName);
	    }
	}
    }


    /*
     * @param request   A reference to the actual <code>HttpServletRequest</code> instance.
     * @param response  A reference to the actual <code>HttpServletResponse</code> instance.
     *
     * @throws ServletException if servlet related operations fail.
     * @throws IOException if i/o operations fail.
     */
    public void service(HttpServletRequest request, HttpServletResponse response)
	throws ServletException, IOException {

	log.debug("service()");
	
	//next page to forward to
	String nextPage = fileUploadLink;

	try {	

	    //deal with multipart
	    String action    = request.getParameter("acton");	
	    log.debug("REQUEST ACTON: " + action);

	    Map params       = null;
            try {
                params = parseCommonsMultipart(request);
                action = (String)params.get("acton");
		log.debug("PARAMS ACTON: " + action);
            } catch (Exception e) {
		log.debug("Request is NOT multipart/form-data", e);
                //request might not be multipart
            }

	    log.debug("FINAL ACTON: " + action);
	    log.debug("REQUEST LENGTH: " + request.getContentLength());

	    //first handle actions that do not require login
	    if ("get".equals(action)) {
		nextPage = dispatchGet(request, response);

	    } else {
		
		//next handle actions that do require login
		//make sure user is logged in and has permission to modify files
		checkLogin(request);
			    
		//forward to the appropriate handler method, based on action
		if ("upload".equals(action)) {
		    nextPage = dispatchUpload(request, response, params);		    
		} 
	    }

	} catch (AccessException ae) {
	    nextPage = handleAccessException(request, response, ae);
	    
	} catch (LoginException le) {
	    nextPage = handleLoginException(request, response, le);

	}
		
	//dispatch back to where it came from
	log.debug("Acton complete, forarding to: " + nextPage);
	RequestDispatcher dispatcher = getServletContext().getRequestDispatcher(nextPage);
	dispatcher.forward(request, response);

	return;
    }

    /**
     * Dispatches <code>upload</code> actions. Creates, updates, or removes 
     * from persistence layer depending on request content.
     */
    protected String dispatchUpload(HttpServletRequest request, HttpServletResponse response, Map params) 
	throws LoginException, AccessException {

	log.debug("***** dispatchUpload *****");
	
	String nextPage = (String)params.get("returnPage");
	request.setAttribute("returnPage", nextPage);
	if (Utils.isEmpty(nextPage)) nextPage = VIEW_MAIN;

	//name variables that need to be available in full method scope
	Database db                     = null;
	FileOutputStream fileOut        = null;
	byte[] fileBytes;

	try {
	    //get request params
	    if (params == null) throw new AppException(MSG_INVALID_PARAMS);

	    Integer parentId    = convertInteger( (String)params.get("parentId"), true );

	    //do the DB transaction
	    db = getJDODatabase();
	    db.begin();

	    //create the new upload 
	    log.debug("about to create new upload: " + UPLOAD_CLASS);
	    Upload upload = (Upload)UPLOAD_CLASS.newInstance();
	    log.debug("Upload: " + upload);

	    //load the parent of this upload
	    if (parentId != null) {
		if (Utils.isEmpty(nextPage)) nextPage = fileUploadLink + parentId;

		Node parent = (Node)db.load(PARENT_CLASS, parentId);

		//set the parent into the upload
		parent.addChild(upload);
	    }

	    //check whether the user has permission to upload
	    checkPermission(request, upload);
	    log.debug("Passed checkPermission(request, upload);");

	    //set the upload into the request for use by the form page if there is an error
	    request.setAttribute("file", upload);

	    String name         = (String)params.get("name");
	    String desc         = (String)params.get("description");
	    Boolean isPublic    = Boolean.valueOf( (String)params.get("isPublic") );
	    
	    //get the actual file, run checks
	    FileItem file       = (FileItem)params.get("attachment");
	    if (file == null)       throw new AppException(MSG_MISSING_FILE);
	    if (file.isFormField()) throw new AppException(MSG_INVALID_FILE);

	    fileBytes           = file.get();
	    long fileSize       = file.getSize();
	    String filename     = file.getName();

	    log.debug("FILE: " + filename );
	    log.debug("FILE SIZE: " + fileSize);
	    log.debug("PUBLIC: " + isPublic);

	    Object[] required = { filename, name };
	    checkRequired( required );

	    //remove any invalid chars in the filename
	    filename = warpFilename(filename);
	    
	    //create the link name
	    String dir       = UPLOAD_DIRECTORY + UIDGenerator.getUID() + FILE_SEPARATOR;
	    String link      = dir + filename;
	    
	    log.debug("LINK: " + link);
	
	    //get a file type that corresponds with the extension
	    FileType type = getFileTypeByExtension(filename, db);

	    //set values into bean	    
	    upload.setFileType(type);
	    upload.setName(name);
	    upload.setLink(link);
	    upload.setDescription(desc);
	    upload.setPublic(isPublic);
	    upload.setSize(fileSize);
	    
	    //Call core functions
	    setNodeFields(upload, request, db);  //saves new upload in DB
	    TaxonomyController.setTerms(upload, request, db);

	    //create the directory if needed
	    java.io.File outDir = new File(location + dir);
	    outDir.mkdirs();
	    
	    //save the file to the filesystem
	    java.io.File outFile  = new File(location + link);
	    fileOut               = new FileOutputStream(outFile);
	    fileOut.write( fileBytes );

	    db.commit();

	    //clear the cache for the parent object
	    if (parentId != null) {
		CacheManager cm = db.getCacheManager();
		cm.expireCache(PARENT_CLASS, parentId);
	    }
	    
	    request.setAttribute("error_message", "File uploaded successfully");

	    nextPage = messageLink + "?link=" + link;

	} catch (AppException sne) {
	    log.debug("", sne);

	    request.setAttribute("error_message", sne.getMessage());

	} catch (PersistenceException e) {
	    handlePersistenceException(request, db, e);

	} catch (IOException ioe) {
	    log.debug("", ioe);

            request.setAttribute("error_message", MSG_IO_ERROR);

	} catch (InstantiationException ie) {
	    //thrown by Class.newInstance()
	    log.debug("", ie);

	    request.setAttribute("error_message", MSG_RUNTIME_ERROR);

	} catch (IllegalAccessException ie) {
	    //thrown by Class.newInstance()
	    log.debug("", ie);

            request.setAttribute("error_message", MSG_RUNTIME_ERROR);

	} finally {
	    closeJDODatabase(db);

	    //close output streams to release memory resources
	    closeStream(fileOut);
        }

	return nextPage;
    }
}
